/*
 * @(#) SqlFileExecutor
 * 版权声明 厦门畅享信息技术有限公司, 版权所有 违者必究
 *
 * <br> Copyright:  Copyright (c) 2018
 * <br> Company:厦门畅享信息技术有限公司
 * <br> @author ningyp
 * <br> 2018-04-26 20:28:52
 * <br> @version 1.0
 * ————————————————————————————————
 *    修改记录
 *    修改者：
 *    修改时间：
 *    修改原因：
 * ————————————————————————————————
 */

package com.sunsharing.sysdb;

import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser;
import com.alibaba.druid.util.JdbcConstants;
import lombok.Data;
import lombok.extern.log4j.Log4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;


/**
 * Created by criss on 2017/3/1.
 */
@Component
@Data
@Log4j
public class SysSqlFileExecutor {

    private JdbcTemplate jdbcTemplate = new JdbcTemplate();

    private String errorSql = "";

    /**
     * 读取 SQL 文件，获取 SQL 语句
     *
     * @param sqlFile SQL 脚本文件
     * @return List 返回所有 SQL 语句的 List
     */
    private List<String> loadSql(File sqlFile) {
        List<String> sqlList = new ArrayList<String>();

        try {
            InputStream sqlFileIn = null;
            log.info(sqlFile.getPath());
            String path = sqlFile.getPath();
            path = path.replaceAll("\\\\", "/");
            log.info(path);
            if (path.startsWith("jar")) {
                int index = path.indexOf("/sql");
                path = path.substring(index);
                sqlFileIn = this.getClass().getResourceAsStream(path);
            } else {
                sqlFileIn = new FileInputStream(sqlFile);
            }

            ByteArrayOutputStream sqlSb = new ByteArrayOutputStream();
            byte[] buff = new byte[1024];
            int byteRead = 0;
            while ((byteRead = sqlFileIn.read(buff)) != -1) {
                sqlSb.write(buff, 0, byteRead);
            }


            // Windows 下换行是 /r/n, Linux 下是 /n
            String[] sqlArr = new String(sqlSb.toByteArray(), "UTF-8").split("(;\\s*\\r\\n)|(;\\s*\\n)");
            for (int i = 0; i < sqlArr.length; i++) {
                String sql = sqlArr[i].replaceAll("--.*", "").trim();
                sql = sql.replace("\uFEFF", "");
                if (!sql.equals("")) {
                    if (sql.endsWith(";")) {
                        sql = sql.substring(0, sql.length() - 1);
                    }
                    sqlList.add(sql);
                }
            }
            return sqlList;
        } catch (Exception ex) {
            log.error("读取初始化SQL文件出错", ex);
            throw new RuntimeException("读取初始化SQL文件出错");
        }
    }

    /**
     * 自建连接，独立事务中执行 SQL 文件
     *
     * @param sqlFile SQL 脚本文件
     * @throws Exception 执行异常
     */
    public void execute(File sqlFile) {
        List<String> sqlList = loadSql(sqlFile);
        List<String> alreadyExecuteSql = new ArrayList<>();
        String endSql = "";//最后执行的一条sql
        String fileName = sqlFile.getName();
        String dataSource = fileName.substring(0, fileName.lastIndexOf("_"));
        jdbcTemplate = DynamicDataSource.getTemplate(dataSource);
        DbTransaction txManager = new DbTransaction(jdbcTemplate.getDataSource());
        txManager.start();
        try {
            for (String sql : sqlList) {
                endSql = sql;
                jdbcTemplate.execute(sql);
                alreadyExecuteSql.add(sql);
            }
            errorSql = "";
            txManager.commit();
        } catch (RuntimeException e) {
            errorSql = endSql;
            log.warn("============已经执行的sql为==================");
            for (String oldSql : alreadyExecuteSql) {
                log.warn("= 【" + oldSql + "】");
            }
            log.info("\n");
            txManager.rollback();

            tableRollBack(alreadyExecuteSql);

            log.warn("============数据初始化出错啦,赶紧处理！！！==============");
            log.warn("=该sql执行失败：【" + endSql + "】");
            log.error("=错误信息为", e);
            throw e;
        }
    }

    /**
     * 表结构相关的回滚
     */
    public void tableRollBack(List<String> alreadyExecuteSql) {
        try {
            int i = 0;
            log.info("============未执行回滚的sql如下，请手动操作并修改正确sql后从新启动==================");
            for (String sql : alreadyExecuteSql) {
                MySqlStatementParser mySqlStatementParser = new MySqlStatementParser(sql);
                String option = mySqlStatementParser.getLexer().token().name;
                if ("CREATE".equals(option)) {
                    creatRollBask(sql);
                    i++;
                } else if ("ALTER".equals(option)) {
                    log.info("############sql【" + sql + "】的alert操作未执行回滚，请手动撤销操作\n");
                } else if ("DROP".equals(option)) {
                    log.info("############sql【" + sql + "】的drop操作未执行回滚，请手动撤销操作");
                }
            }
            log.info("表结构相关的回滚执行结束,回滚次数：" + i);
        } catch (RuntimeException threadError) {
            throw threadError;
        }
    }

    /**
     * 建表回滚
     *
     * @param sql 已经执行的sql
     */
    public void creatRollBask(String sql) {
        List<SQLStatement> sqlStatements = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL);
        String tableName = ((MySqlCreateTableStatement) sqlStatements.get(0)).getName().getSimpleName();
        jdbcTemplate.execute("DROP TABLE " + tableName);
        log.info("数据表【" + tableName + "】的create操作，回e滚成功！");
    }

}
